package nl.weeaboo.sh.editor;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.TexturePaint;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

import nl.weeaboo.sh.editor.HitBoxModeSelector.Mode;
import nl.weeaboo.sh.editor.area.ChangeListener;
import nl.weeaboo.sh.model.CharaImageModel;
import nl.weeaboo.sh.model.ColCircle;
import nl.weeaboo.sh.model.DamageRect;
import timon.common.GraphicsUtil;
import timon.common.Log;

@SuppressWarnings("serial")
public class HitBoxPanel extends JPanel {
	
	private static final float zoomTable[] = new float[] {
		0.05f, 0.10f, 0.25f, 0.50f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f
	};

	private List<ChangeListener> listeners;
	private BufferedImage background;
	private BufferedImage image;
	private BufferedImage checkerboardImage;
	private BufferedImage buffer;
	private CharaImageModel charaImage;

	private Font font;
	private String statusText;
	private float tx, ty;
	private int zoom;
	private Point clickPoint;
	
	private HitBoxModeSelector.Mode mode = Mode.ANCHOR;
	private int rectIndex;
	private Rectangle levelRect;
	
	public HitBoxPanel(Rectangle lr) {
		levelRect = lr;

		listeners = new LinkedList<ChangeListener>();
		try {
			checkerboardImage = ImageIO.read(HitBoxPanel.class.getResource("checkerboard.png"));
		} catch (IOException e) {
			Log.showError(e);
		}

		font = new Font("arial", Font.BOLD, 14);
		statusText = "";
		zoom = 9;
		
		addInputListeners();
		
		setPreferredSize(new Dimension(512, 512));		
	}
	
	//Functions
	private void addInputListeners() {
		addMouseListener(new MouseListener() {
			public void mouseClicked(MouseEvent e) {
			}
			public void mouseEntered(MouseEvent e) {
				requestFocus();
			}
			public void mouseExited(MouseEvent e) {
			}
			public void mousePressed(MouseEvent e) {
				clickPoint = e.getPoint();
			}
			public void mouseReleased(MouseEvent e) {
				clickPoint = null;
			}
		});
		addMouseMotionListener(new MouseMotionListener() {
			public void mouseDragged(MouseEvent e) {
				if (clickPoint == null) return;
				
				if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
					tx += (e.getX() - clickPoint.x) * getZoom();
					ty += (e.getY() - clickPoint.y) * getZoom();
					
					if (!contains(e.getPoint())) {
						int x = e.getX();
						int y = e.getY();
						if (e.getX() < 0) x = getWidth() + e.getX();
						if (e.getX() >= getWidth()) x = e.getX() - getWidth() - 1;
						if (e.getY() < 0) y = getHeight() + e.getY();
						if (e.getY() >= getHeight()) y = e.getY() - getHeight() - 1;
					
						Point los = getLocationOnScreen();
						clickPoint = new Point(x, y);
						try {
							Robot r = new Robot();
							r.mouseMove(los.x + x, los.y + y);
						} catch (AWTException awte) {							
						}
					} else {
						clickPoint = e.getPoint();
					}
					
					redraw();
				}
			}
			public void mouseMoved(MouseEvent e) {
			}
		});
		
		setFocusable(true);
		addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				float oldZoom = zoomTable[zoom];

				int key = e.getKeyCode();
				if (key == KeyEvent.VK_ADD) {					
					if (zoom < zoomTable.length-1) {
						zoom++;
					}
					
					tx = zoomTable[zoom] * (tx / oldZoom);
					ty = zoomTable[zoom] * (ty / oldZoom);
					
					redraw();
				} else if (key == KeyEvent.VK_SUBTRACT) {
					if (zoom > 0) {
						zoom--;
					}
					
					tx = zoomTable[zoom] * (tx / oldZoom);
					ty = zoomTable[zoom] * (ty / oldZoom);

					redraw();
				}

				if (key == KeyEvent.VK_LEFT) {
					if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0) {
						transformSelection(0, 0, -1, 0);
					} else {
						transformSelection(-1, 0, 0, 0);
					}
				} else if (key == KeyEvent.VK_RIGHT) {
					if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0) {
						transformSelection(0, 0, 1, 0);
					} else {
						transformSelection(1, 0, 0, 0);
					}
				} else if (key == KeyEvent.VK_UP) {
					if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0) {
						transformSelection(0, 0, 0, -1);
					} else {
						transformSelection(0, -1, 0, 0);
					}
				} else if (key == KeyEvent.VK_DOWN) {
					if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0) {
						transformSelection(0, 0, 0, 1);
					} else {
						transformSelection(0, 1, 0, 0);
					}
				}
				
				repaint();
			}
			public void keyReleased(KeyEvent e) {
			}
			public void keyTyped(KeyEvent e) {
			}
		});
	}

	public void addChangeListener(ChangeListener l) {
		listeners.add(l);
	}
	public void removeChangeListener(ChangeListener l) {
		listeners.remove(l);
	}

	protected void fireImageChanged(final CharaImageModel image) {
		ChangeListener ls[] = listeners.toArray(new ChangeListener[listeners.size()]);
		for (ChangeListener l : ls) {
			l.onChanged(image);
		}
	}

	public void redrawBackground() {
		if (image == null) {
			background = null;
			redraw();
			return;
		}
		
		int w = image.getWidth();
		int h = image.getHeight();
		background = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		
		Graphics2D g = (Graphics2D)background.getGraphics();
		g.setPaint(new TexturePaint(checkerboardImage, new Rectangle2D.Float(0, 0,
				checkerboardImage.getWidth(), checkerboardImage.getHeight())));
		g.fillRect(0, 0, w, h);
		g.drawImage(image, 0, 0, this);		
		
		redraw();
	}
	public void redraw() {
		if (buffer == null) return;
		
		int w = getWidth();
		int h = getHeight();
		Graphics2D g = (Graphics2D)buffer.getGraphics();
		g.setBackground(Color.BLACK);
		g.clearRect(0, 0, w, h);
		
		AffineTransform oldTransform = g.getTransform();
		
		if (background != null) {
			g.translate(tx, ty);
			g.scale(getZoom(), getZoom());
			g.drawImage(background, 0, 0, this);
		}

		g.setTransform(oldTransform);
		
		repaint();
	}

	protected void paintComponent(Graphics graphics) {
		super.paintComponent(graphics);

		Graphics2D g = (Graphics2D)graphics;
		if (buffer != null) {
			g.drawImage(buffer, 0, 0, this);
		}
		
		if (charaImage == null) return;
		
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		AffineTransform oldTransform = g.getTransform();
		g.translate(tx, ty);
		g.scale(getZoom(), getZoom());

		CharaImageModel ci = charaImage;
		int x = ci.anchor.x;
		int y = ci.anchor.y;
		
		Mode modes[] = Mode.values();
		for (int n = 1; n < modes.length-1; n++) {
			if (modes[n-1] == mode || modes[n-1] == null) {
				modes[n-1] = modes[n];
				modes[n] = null;
			}
		}
		if (modes[modes.length-2] == null) modes[modes.length-2] = mode;		
		
		g.setColor(GraphicsUtil.setAlpha(Color.GREEN, 100));
		g.fillRect(x+levelRect.x, y+levelRect.y, levelRect.width, levelRect.height);

		for (Mode m : modes) {
			int alpha = (mode == m ? 200 : 100);
			
			if (m == Mode.ANCHOR) {
				g.setColor(Color.RED);
				g.fillRect(x, y, 1, 1);
			} else if (m == Mode.IMAGE) {
				g.setColor(GraphicsUtil.setAlpha(Color.WHITE, alpha));
				g.fillRect(x+ci.imageBounds.x, y+ci.imageBounds.y, ci.imageBounds.width, ci.imageBounds.height);
			} else if (m == Mode.HIT_RECT) {			
				g.setColor(GraphicsUtil.setAlpha(Color.BLUE, alpha));
				for (Rectangle r : ci.hitRects) {
					g.fillRect(x+r.x, y+r.y, r.width, r.height);
				}
			} else if (m == Mode.DAMAGE_RECT) {			
				for (int n = 0; n < ci.damageRects.size(); n++) {
					Rectangle r = ci.damageRects.get(n);
					g.setColor(GraphicsUtil.setAlpha(Color.RED, alpha));
					g.fillRect(x+r.x, y+r.y, r.width, r.height);
					
					if (mode == m && n == rectIndex) {
						g.setColor(GraphicsUtil.setAlpha(Color.RED.darker(), alpha));
						g.drawRect(x+r.x, y+r.y, r.width, r.height);
					}
				}
			} else if (m == Mode.SPLASH_RECT) {			
				for (int n = 0; n < ci.splashRects.size(); n++) {
					Rectangle r = ci.splashRects.get(n);
					g.setColor(GraphicsUtil.setAlpha(Color.ORANGE, alpha));
					g.fillRect(x+r.x, y+r.y, r.width, r.height);
					
					if (mode == m && n == rectIndex) {
						g.setColor(GraphicsUtil.setAlpha(Color.ORANGE.darker(), alpha));
						g.drawRect(x+r.x, y+r.y, r.width, r.height);
					}
				}
			} else if (m == Mode.COL_CIRCLE) {
				for (int n = 0; n < ci.colCircles.size(); n++) {
					ColCircle c = ci.colCircles.get(n);
					int r = c.getRadius();
					
					g.setColor(GraphicsUtil.setAlpha(Color.PINK, alpha));
					g.fillOval(x+c.x-r, y+c.y-r, 2*r, 2*r);
					
					g.setColor(GraphicsUtil.setAlpha(Color.PINK.darker(), alpha));
					if (mode == m && n == rectIndex) {
						g.drawOval(x+c.x-r, y+c.y-r, 2*r, 2*r);
						g.setColor(Color.PINK.darker());
					}
					g.fillRect(x+c.x, y+c.y, 1, 1);
				}
			}
		}
		
		g.setTransform(oldTransform);
		
		g.setFont(font);
		g.setColor(Color.BLACK);
		g.drawString(statusText, 1+10, 1+18);
		g.setColor(Color.WHITE);
		g.drawString(statusText, 10, 18);		
	}
		
	protected void transformSelection(int dx, int dy, int dw, int dh) {
		if (charaImage == null) return;
		
		CharaImageModel ci = charaImage;
		if (mode == Mode.ANCHOR) {
			ci.anchor.x += dx;
			ci.anchor.y += dy;			
		} else if (mode == Mode.COL_CIRCLE) {
			ColCircle c = getColCircle();
			c.x += dx;
			c.y += dy;
			c.setRadius(Math.max(0, c.getRadius()+dw+dh));
		}
		
		Rectangle rect = getRect();
		if (rect != null) {
			rect.x += dx;
			rect.y += dy;
			rect.width = Math.max(0, rect.width+dw);
			rect.height = Math.max(0, rect.height+dh);
		}
		
		fireImageChanged(ci);
		updateStatusText();			
	}
	
	protected void updateStatusText() {
		statusText = "";
		if (charaImage == null) {
			repaint();
			return;
		}
		
		if (mode == Mode.ANCHOR) {
			statusText = String.format("(%d, %d)", charaImage.anchor.x, charaImage.anchor.y);
		} else if (mode == Mode.COL_CIRCLE) {
			ColCircle circle = getColCircle();
			if (circle != null) {
				int r = circle.getRadius();
				statusText = String.format("c=(%d, %d) r=(%d) b=(%d, %d, %d, %d)", circle.x, circle.y, r,
						circle.x-r, circle.y-r, 2*r, 2*r);
			}
		} else {
			Rectangle rect = getRect();
			if (rect != null) {
				statusText = String.format("(%d, %d, %d, %d)", rect.x, rect.y, rect.width, rect.height);
			}
		}

		repaint();
	}
	
	//Getters
	protected float getZoom() { return zoomTable[zoom]; }

	private Rectangle getRect() {
		CharaImageModel ci = charaImage;
		if (mode == Mode.IMAGE) {
			return ci.imageBounds;
		} else if (mode == Mode.HIT_RECT) {
			while (rectIndex >= ci.hitRects.size()) ci.hitRects.add(new Rectangle());
			return ci.hitRects.get(rectIndex);
		} else if (mode == Mode.DAMAGE_RECT) {
			while (rectIndex >= ci.damageRects.size()) ci.damageRects.add(new DamageRect());
			return ci.damageRects.get(rectIndex);
		} else if (mode == Mode.SPLASH_RECT) {
			while (rectIndex >= ci.splashRects.size()) ci.splashRects.add(new DamageRect());
			return ci.splashRects.get(rectIndex);
		}		
		return null;
	}
	private ColCircle getColCircle() {
		CharaImageModel ci = charaImage;
		
		if (mode == Mode.COL_CIRCLE) {
			while (rectIndex >= ci.colCircles.size()) ci.colCircles.add(new ColCircle());
			return ci.colCircles.get(rectIndex);
		}
		return null;
	}

	//Setters
	public void setMode(HitBoxModeSelector.Mode m, int ri) {
		if (mode != m || rectIndex != ri) {
			mode = m;
			rectIndex = ri;

			updateStatusText();			
			repaint();
		}
	}
	public void setCharaImage(Rectangle lr, CharaImageModel ci) {
		if (charaImage != ci) {
			charaImage = ci;
			levelRect = lr;

			redraw();
			updateStatusText();
		}
	}
	public void setImage(BufferedImage i) {
		if (image != i) {
			image = i;
			redrawBackground();
			updateStatusText();
		}
	}
	public void setBounds(int x, int y, int w, int h) {
		super.setBounds(x, y, w, h);

		if (w == 0 || h == 0) {
			buffer = null;
		} else if (buffer == null || buffer.getWidth() != w || buffer.getHeight() != h) {
			buffer = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
			
			redrawBackground();
		}
	}
	
}
